#include <QPainter>
#include <QSettings>
#include "VideoView.hxx"
#include "Sequence.hxx"
#include "FramePool.hxx"
#include "VideoThread.hxx"
#include "VideoController.hxx"
#include "TrackedObject.hxx"
#include "Project.hxx"
#include "MainWindow.hxx"

int cursorDist = 10;

//extern QSettings* __globalSettings;

VideoView::VideoView(QWidget *parent):QWidget(0)
{
    _imageData = 0;

	_timer = new QTimer();
    _timer->setInterval(15);
	
    connect(_timer,SIGNAL(timeout()),this,SLOT(updateFrame()));

	_timer->start();

	_dragingRect = false;

	_status = VW_STATUS_DISPLAYING;

	setMouseTracking(true);
	setMinimumHeight(400);
	setMinimumWidth(600);
}

void VideoView::paintEvent(QPaintEvent *event)
{
	if (!_imageData)
		return;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

    QImage image(_imageData + sizeof(VideoFrameInfo), sequence->getVideoWidth(), sequence->getVideoHeight(),QImage::Format_RGB888);

	QRect rect = QRect(0, 0, geometry().width(), geometry().height());

    QPainter painter(this);
	painter.drawImage(rect, image.rgbSwapped(), QRect(0,0,image.width(),image.height()));

	drawNewObject(painter);

	drawTrackingObject(painter);

	drawTrackedObject(painter);

	drawTextInfo(painter);

    painter.end();
}

void VideoView::updateFrame()
{
	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

    if( !_imageData )
    {
        int imgWidth = sequence->getVideoWidth();
        int imgHeight = sequence->getVideoHeight();

        if( imgWidth < 1 || imgHeight < 1 )
            return;

		FramePool* pool = sequence->getFramePool();

        _imageData = (uchar*)malloc(pool->getPageSize());

        return;
    }
	
	if (sequence->getTrackedFramePool()->popFrame((char*)_imageData))
	{
		VideoFrameInfo* vfi = (VideoFrameInfo*)_imageData;

		_frameIndex = vfi->FrameIndex;

		sequence->setCurrentFrameIndex(_frameIndex);

		sequence->setCurrentTimestamp(vfi->TimeStamp);

		update();

		emit frameInfoUpdated();
	}
}

void VideoView::mousePressEvent(QMouseEvent *event)
{
	if (_status == VW_STATUS_DISPLAYING)
	{
		if (event->buttons() & Qt::LeftButton)
		{
			_startPoint = event->pos();
			_endPoint = _startPoint;
			_dragingRect = true;
		}
	}
	else if (_status = VW_STATUS_EDITING)
	{
		Qt::CursorShape shape = cursor().shape();

		if (shape == Qt::SizeAllCursor)
		{
			_startPoint = event->pos();
			_endPoint = _startPoint;
		}
		else if (shape == Qt::SizeHorCursor)
		{
			_startPoint = event->pos();
			_endPoint = _startPoint;
		}
		else if (shape == Qt::SizeVerCursor)
		{
			_startPoint = event->pos();
			_endPoint = _startPoint;
		}
	}
}

void VideoView::mouseMoveEvent(QMouseEvent *event)
{
	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

	if (_status == VW_STATUS_DISPLAYING)
	{
		if (event->buttons() & Qt::LeftButton)
		{
			if (_dragingRect)
			{
				_endPoint = event->pos();
				update();
			}
		}
	}
	else if (_status == VW_STATUS_EDITING)
	{
		if (event->buttons() == Qt::NoButton)
		{
			QPoint mpt = event->pos();

			std::vector<TrackedObject*>* objects = sequence->getTrackedObjects();

			bool horizentalCursor = false;
			bool verticalCursor = false;

			//// North East cursor
			//bool neCursor = false;

			//// North West cursor
			//bool nwCursor = false;

			// Moving cursor
			bool mvCursor = false;

			for (int i = 0; i < objects->size(); i++)
			{
				TrackedObject* tobj = objects->at(i);

				if (tobj->isSelected())
				{
					cv::Rect2d crect = tobj->getRect(_frameIndex);
					QRect qrect = cvRect2QRect(crect);

					if (((abs(qrect.x() - mpt.x()) < cursorDist) || (abs(qrect.x() + qrect.width() - mpt.x()) < cursorDist))
						&& ((mpt.y() - qrect.y() > cursorDist) && (qrect.y() + qrect.height() - cursorDist > mpt.y())))
						horizentalCursor = true;
					else if (((abs(qrect.y() - mpt.y()) < cursorDist) || (abs(qrect.y() + qrect.height() - mpt.y()) < cursorDist))
						&& ((mpt.x() - qrect.x() > cursorDist) && (qrect.x() + qrect.width() - cursorDist > mpt.x())))
						verticalCursor = true;
					else if ((mpt.x() > qrect.x() + cursorDist) &&
						(qrect.x() + qrect.width() - cursorDist > mpt.x()) &&
						(mpt.y() > qrect.y() + cursorDist) &&
						(qrect.y() + qrect.height() - cursorDist > mpt.y()))
						mvCursor = true;
				}
			}

			if (horizentalCursor)
				setCursor(QCursor(Qt::SizeHorCursor));
			else if (verticalCursor)
				setCursor(QCursor(Qt::SizeVerCursor));
			else if (mvCursor)
				setCursor(QCursor(Qt::SizeAllCursor));
			else
				setCursor(QCursor(Qt::ArrowCursor));
		}
		else if( event->buttons() & Qt::LeftButton )
		{
			Qt::CursorShape shape = cursor().shape();

			if (shape == Qt::SizeAllCursor)
			{
				_endPoint = event->pos();
			}
			else if (shape == Qt::SizeHorCursor)
			{
				_endPoint = event->pos();
			}
			else if (shape == Qt::SizeVerCursor)
			{
				_endPoint = event->pos();
			}

			update();
		}
	}
}

void VideoView::mouseReleaseEvent(QMouseEvent *event)
{	
	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

	if (_status == VW_STATUS_DISPLAYING)
	{
		if (_dragingRect)
		{
			_endPoint = event->pos();

			if( abs(_startPoint.x() - _endPoint.x()) > 20 && abs( _startPoint.y() - _endPoint.y()) > 20 )
				_rects.push_back(QRect(_startPoint, _endPoint));

			_dragingRect = false;

			if (_rects.size() > 0)
				emit rectSelected();
		}
	}
	else if( _status == VW_STATUS_EDITING )
	{
		_endPoint = event->pos();

		if (event->button() == Qt::LeftButton)
		{
			TrackedObject* tobj = nullptr;
			std::vector<TrackedObject*>* objects = sequence->getTrackedObjects();

			for (int i = 0; i < objects->size(); i++)
			{
				tobj = objects->at(i);

				if (tobj->isSelected())
					break;
			}

			if (tobj == nullptr)
				return;

			Qt::CursorShape shape = cursor().shape();
			
			if (shape == Qt::SizeAllCursor)
			{
				cv::Rect2d rect = tobj->getRect(_frameIndex);

				QPoint pt = _endPoint - _startPoint;

				cv::Point2d cpt = qPoint2cvPoint(pt);

				rect.x += cpt.x;
				rect.y += cpt.y;

				tobj->pushRect(_frameIndex, rect);

				_startPoint = _endPoint;
			}
			else if (shape == Qt::SizeHorCursor)
			{
				cv::Rect2d crect = tobj->getRect(_frameIndex);
				QRect qrect = cvRect2QRect(crect);

				QPoint pt = _endPoint - _startPoint;
				cv::Point2d cpt = qPoint2cvPoint(pt);

				QRect trect;

				if (abs(_startPoint.x() - qrect.x()) <= cursorDist)
				{
					trect.setX(qrect.x() + pt.x());
					trect.setY(qrect.y());
					trect.setWidth(qrect.width() - pt.x());
					trect.setHeight(qrect.height());
				}
				else
				{
					trect.setX(qrect.x());
					trect.setY(qrect.y());
					trect.setWidth(qrect.width() + pt.x());
					trect.setHeight(qrect.height());
				}

				cv::Rect2d nrect = qRect2cvRect(trect);
				tobj->pushRect(_frameIndex, nrect);
				_startPoint = _endPoint;
			}
			else if (shape == Qt::SizeVerCursor)
			{
				cv::Rect2d crect = tobj->getRect(_frameIndex);
				QRect qrect = cvRect2QRect(crect);

				QPoint pt = _endPoint - _startPoint;
				cv::Point2d cpt = qPoint2cvPoint(pt);

				QRect trect;

				if (abs(_startPoint.y() - qrect.y()) <= cursorDist)
				{
					trect.setX(qrect.x());
					trect.setY(qrect.y() + pt.y());
					trect.setWidth(qrect.width());
					trect.setHeight(qrect.height() - pt.y());
				}
				else
				{
					trect.setX(qrect.x());
					trect.setY(qrect.y());
					trect.setWidth(qrect.width());
					trect.setHeight(qrect.height() + pt.y());
				}

				cv::Rect2d nrect = qRect2cvRect(trect);
				tobj->pushRect(_frameIndex, nrect);
				_startPoint = _endPoint;
			}
			
			update();
		}
	}
}

QRect VideoView::cvRect2QRect(cv::Rect2d rect)
{
	QRect geoRect = geometry();

	QRect result;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return result;	

	result.setX(rect.x / sequence->getVideoWidth() * geoRect.width());
	result.setY(rect.y / sequence->getVideoHeight() * geoRect.height());
	result.setWidth(rect.width / sequence->getVideoWidth() * geoRect.width());
	result.setHeight(rect.height / sequence->getVideoHeight() * geoRect.height());

	return result;
}

cv::Rect2d VideoView::qRect2cvRect(QRect rect)
{
	QRect geoRect = geometry();

	cv::Rect2d result;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return result;

	result.x = rect.x() * 1.0 / geoRect.width() * sequence->getVideoWidth();
	result.y = rect.y() * 1.0 / geoRect.height() * sequence->getVideoHeight();

	result.width = rect.width() * 1.0 / geoRect.width() * sequence->getVideoWidth();
	result.height = rect.height() * 1.0 / geoRect.height() * sequence->getVideoHeight();

	if (result.width < 0.0)
	{
		result.x += result.width;
		result.width = -result.width;
	}

	if (result.height < 0.0)
	{
		result.y += result.height;
		result.height = -result.height;
	}

	return result;
}

cv::Point2d VideoView::qPoint2cvPoint(QPoint qpt)
{
	cv::Point2d pt;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return pt;

	pt.x = qpt.x() * 1.0 / width() * sequence->getVideoWidth();
	pt.y = qpt.y() * 1.0 / height() * sequence->getVideoHeight();

	return pt;
}

QPoint VideoView::cvPoint2QPoint(cv::Point2d cpt)
{
	QPoint pt;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return pt;

	pt.setX(cpt.x / sequence->getVideoWidth() * width());
	pt.setY(cpt.y / sequence->getVideoHeight() * height());

	return pt;
}

std::vector<cv::Rect2d> VideoView::getCvRects()
{
	std::vector<cv::Rect2d> results;

	for (int i = 0; i < _rects.size(); i++)
	{
		results.push_back(qRect2cvRect(_rects.at(i)));
	}

	return results;
}

void VideoView::drawNewObject(QPainter& painter)
{
	QPen pen;
	pen.setStyle(Qt::SolidLine);
	pen.setWidth(3);
	pen.setColor(QColor(255, 255, 0, 127));
	painter.setPen(pen);

	if (_dragingRect)
	{
		painter.drawRect(QRect(_startPoint, _endPoint));
	}

	for (int i = 0; i < _rects.size(); i++)
	{		
		painter.drawRect(_rects.at(i));
	}
}

void VideoView::drawTrackingObject(QPainter& painter)
{
	//Draw Tracking Box
	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

	std::vector<TrackedObject*>* objects = sequence->getTrackingObjects();

	QPen pen;
	pen.setStyle(Qt::SolidLine);
	pen.setWidth(3);
	pen.setColor(QColor(255, 0, 0, 127));
	painter.setPen(pen);

	for (int i = 0; i < objects->size(); i++)
	{
		TrackedObject* tobj = objects->at(i);

		cv::Rect2d crect = tobj->getRect(_frameIndex);

		QRect rect = cvRect2QRect(crect);

		painter.drawRect(rect);
	}
}

void VideoView::drawTrackedObject(QPainter& painter)
{
	//Draw Tracking Box
	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

	std::vector<TrackedObject*>* objects = sequence->getTrackedObjects();

	QPen pen;
	pen.setStyle(Qt::SolidLine);
	pen.setWidth(3);
	pen.setColor(QColor(0, 0, 255, 127));
	painter.setPen(pen);

	bool noSelectedObjectToDraw = true;

	for (int i = 0; i < objects->size(); i++)
	{
		TrackedObject* tobj = objects->at(i);

		if (!tobj->isVisible()) continue;

		cv::Rect2d crect = tobj->getRect(_frameIndex);

		if (crect.x == 0.0 && crect.y == 0.0 && crect.width == 0.0 && crect.height == 0.0)
			continue;

		int classIndex = tobj->getClassIndex();
		std::vector<Class*>* classes = _project->getClasses();

		QString objectClassName = QString("Object");

		for (int j = 0; j < classes->size(); j++)
		{
			Class* cls = classes->at(j);

			if (cls->getId() == classIndex)
			{
				objectClassName = cls->getName().c_str();
				break;
			}
		}

		noSelectedObjectToDraw = false;

		QRect rect = cvRect2QRect(crect);

		QFont font("Times", 12);
		font.setBold(true);
		QFontMetrics fm(font);
		QString textString = QString("%1:%2").arg(objectClassName).arg(tobj->getObjectId());

		int textWidthInPixels = fm.width(textString);
		int textHeightInPixels = fm.height();
		painter.setFont(font);

		QRect textRect;
		textRect.setX(rect.x());
		textRect.setY(rect.y() - textHeightInPixels - 10 );
		textRect.setHeight(textHeightInPixels + 10 );
		textRect.setWidth(textWidthInPixels + 10 );

		painter.setBrush(Qt::SolidPattern);
		painter.setPen(Qt::NoPen);
		painter.setBrush(QColor(0, 0, 255, 127));
		painter.drawRect(textRect);

		painter.setPen(QColor(255, 255, 0, 255));
		painter.drawText(textRect.x()+5, rect.y() - 5, textString);

		painter.setBrush(Qt::NoBrush);
		painter.setPen(pen);
		painter.drawRect(rect);

		if (tobj->isSelected())
		{
			drawSelectedObject(painter, rect);
		}
	}

	if (noSelectedObjectToDraw)
	{
		_status = VW_STATUS_DISPLAYING;
		sequence->desectAllTrackedObjects();
	}
}

void VideoView::drawSelectedObject(QPainter& painter, QRect& rect)
{
	int rectWidth = 9;

	// rect 0
	QRect drect;
	
	drect.setX(rect.x() - rectWidth / 2);
	drect.setY(rect.y() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);

	painter.drawRect(drect);


	// rect 1
	drect.setX(rect.x() + rect.width() / 2 - rectWidth / 2);
	drect.setY(rect.y() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 2
	drect.setX(rect.x() + rect.width() - rectWidth / 2);
	drect.setY(rect.y() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 3
	drect.setX(rect.x() - rectWidth / 2);
	drect.setY(rect.y() + rect.height() / 2 - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 4
	//drect.setX(rect.x() + rect.width() / 2 - rectWidth / 2);
	//drect.setY(rect.y() + rect.height() / 2 - rectWidth / 2);
	//drect.setWidth(rectWidth);
	//drect.setHeight(rectWidth);
	//painter.drawRect(drect);

	// rect 5
	drect.setX(rect.x() + rect.width() - rectWidth / 2);
	drect.setY(rect.y() + rect.height() / 2 - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 6
	drect.setX(rect.x() - rectWidth / 2);
	drect.setY(rect.y() + rect.height() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 7
	drect.setX(rect.x() + rect.width() / 2 - rectWidth / 2);
	drect.setY(rect.y() + rect.height() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// rect 8
	drect.setX(rect.x() + rect.width() - rectWidth / 2);
	drect.setY(rect.y() + rect.height() - rectWidth / 2);
	drect.setWidth(rectWidth);
	drect.setHeight(rectWidth);
	painter.drawRect(drect);

	// draw editing rect
	Qt::CursorShape shape = cursor().shape();

	if (shape == Qt::SizeAllCursor)
	{
		QRect trect;
		trect.setX(rect.x() + (_endPoint.x() - _startPoint.x()));
		trect.setY(rect.y() + (_endPoint.y() - _startPoint.y()));
		trect.setWidth(rect.width());
		trect.setHeight(rect.height());

		painter.drawRect(trect);
	}
	else if (shape == Qt::SizeHorCursor)
	{
		int tx = _endPoint.x() - _startPoint.x();
		QRect trect;

		if (abs(_startPoint.x() - rect.x()) <= cursorDist)
		{
			trect.setX(rect.x() + tx);
			trect.setY(rect.y());
			trect.setWidth(rect.width() - tx);
			trect.setHeight(rect.height());
		}
		else
		{
			trect.setX(rect.x());
			trect.setY(rect.y());
			trect.setWidth(rect.width() + tx);
			trect.setHeight(rect.height());		
		}
		painter.drawRect(trect);
	}
	else if (shape == Qt::SizeVerCursor)
	{
		int ty = _endPoint.y() - _startPoint.y();
		QRect trect;

		if (abs(_startPoint.y() - rect.y()) <= cursorDist)
		{
			trect.setX(rect.x());
			trect.setY(rect.y() + ty);
			trect.setWidth(rect.width());
			trect.setHeight(rect.height() - ty);
		}
		else
		{
			trect.setX(rect.x());
			trect.setY(rect.y());
			trect.setWidth(rect.width());
			trect.setHeight(rect.height() + ty);
		}

		painter.drawRect(trect);
	}
}

void VideoView::drawTextInfo(QPainter &painter)
{

	QFont font("Times", 18);
	font.setBold(true);
	QFontMetrics fm(font);
	QString textString = QString("Frame:%1").arg(_frameIndex);
	int textWidthInPixels = fm.width(textString);
	int textHeightInPixels = fm.height();
	painter.setFont(font);

	QRect canvas = geometry();
	QPoint pt;
	pt.setX(canvas.width() - textWidthInPixels);
	pt.setY(textHeightInPixels);

	QRect rect(pt.x(), pt.y() - textHeightInPixels + 5, textWidthInPixels, textHeightInPixels);
	painter.setBrush(Qt::black);
	painter.setPen(Qt::black);
	painter.drawRect(rect);
	
	painter.setBrush(Qt::red);
	painter.setPen(Qt::red);
	painter.drawText(pt, textString);	
}

void VideoView::mouseDoubleClickEvent(QMouseEvent *event)
{
	if (!_controller->isPaused())
		return;

	Sequence* sequence = _project->getActiveSequence();

	if (!sequence) return;

	QPoint pt = event->pos();
	cv::Point2d cpt = qPoint2cvPoint(pt);

	std::vector<TrackedObject*>* objects = sequence->getTrackedObjects();

	bool rectSelectedFlag = false;

	for (int i = 0; i < objects->size(); i++)
	{
		TrackedObject* obj = objects->at(i);

		cv::Rect2d orect = obj->getRect(_frameIndex);

		if ((cpt.x <= orect.x + orect.width) &&
			(cpt.x >= orect.x) &&
			(cpt.y <= orect.y + orect.height) &&
			(cpt.y >= orect.y))
		{
			obj->select();
			rectSelectedFlag = true;
		}
		else
		{
			obj->deselect();
		}
	}

	if (rectSelectedFlag)
	{
		_status = VW_STATUS_EDITING;
		clearRects();
		emit objectSelected();
	}
	else
	{
		_status = VW_STATUS_DISPLAYING;
		emit objectUnSelected();
	}
	
	update();
}

void VideoView::reset()
{
	if (_imageData)
	{
		free(_imageData);
		_imageData = 0;
	}
}
